{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/workshops/Taiwan_2024.ipynb)\n",
    "\n",
    "**3D Mapping with Leafmap and MapLibre**\n",
    "\n",
    "-   Registration: https://shorturl.at/4J0HW\n",
    "-   Notebook: https://leafmap.org/workshops/Taiwan_2024\n",
    "-   Leafmap: https://leafmap.org\n",
    "\n",
    "## Introduction\n",
    "\n",
    "This notebook is designed for workshop presented at the [Center for GIS, RCHSS, Academia Sinica](https://gis.rchss.sinica.edu.tw/), Taiwan, on August 7, 2024. Part 1 of the workshop will introduce the Earth Engine Python API and the geemap Python package. Part 2 will focus on 3D mapping with Leafmap and MapLibre.\n",
    "\n",
    "### Prerequisites\n",
    "\n",
    "-   A Google account to access Google Colab\n",
    "-   A [MapTiler API key](https://docs.maptiler.com/cloud/api/authentication-key/) to access MapTiler vector tiles\n",
    "\n",
    "### Agenda\n",
    "\n",
    "The main topics to be covered in this workshop include:\n",
    "\n",
    "-   Create interactive 3D maps\n",
    "-   3D terrain, 3D buildings, and 3D indoor mapping\n",
    "-   Visualize local vector and raster datasets\n",
    "-   Visualize geospatial data in the cloud (COG, STAC, PMTiles)\n",
    "-   Add custom components to the map\n",
    "-   Export 3D maps as HTML files for website hosting"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## Installation\n",
    "\n",
    "Uncomment the following line to install the required Python packages."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# %pip install -U \"leafmap[maplibre]\" geemap"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "Import the maplibre mapping backend."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "import leafmap.maplibregl as leafmap"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5",
   "metadata": {},
   "source": [
    "## Set up API Key\n",
    "\n",
    "To run this notebook, you need to set up a MapTiler API key. You can get a free API key by signing up at [https://cloud.maptiler.com/](https://cloud.maptiler.com/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import os\n",
    "# os.environ[\"MAPTILER_KEY\"] = \"YOUR_API_KEY\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "## Create interactive maps\n",
    "\n",
    "### Create a simple map\n",
    "\n",
    "Let's create a simple interactive map using Leafmap."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map()\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "You can customize the map by specifying map center [lon, lat], zoom level, pitch, and bearing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100, 40], zoom=3, pitch=0, bearing=0)\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11",
   "metadata": {},
   "source": [
    "To customize the basemap, you can specify the `style` parameter. It can be an URL or a string, such as `dark-matter`, `positron`, `voyager`, `demotiles`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"positron\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13",
   "metadata": {},
   "source": [
    "To create a map with a background color, use `style=\"background-<COLOR>\"`, such as `background-lightgray` and `background-green`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"background-lightgray\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "Alternatively, you can provide a URL to a vector style."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "style = \"https://demotiles.maplibre.org/style.json\"\n",
    "m = leafmap.Map(style=style)\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "### Add map controls\n",
    "\n",
    "The control to add to the map. Can be one of the following: `scale`, `fullscreen`, `geolocate`, `navigation`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map()\n",
    "m.add_control(\"geolocate\", position=\"top-left\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {},
   "source": [
    "### Add basemaps\n",
    "\n",
    "You can add basemaps to the map using the `add_basemap` method. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map()\n",
    "m.add_basemap(\"OpenTopoMap\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.add_basemap(\"Esri.WorldImagery\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map()\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23",
   "metadata": {},
   "source": [
    "To add basemaps interactively, use the `add_basemap` method without specifying the `basemap` parameter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.add_basemap()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25",
   "metadata": {},
   "source": [
    "### Add XYZ tile layer\n",
    "\n",
    "You can add XYZ tile layers to the map using the `add_tile_layer` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map()\n",
    "url = \"https://tile.openstreetmap.org/{z}/{x}/{y}.png\"\n",
    "m.add_tile_layer(\n",
    "    url, name=\"OpenStreetMap\", attribution=\"OpenStreetMap\", opacity=1.0, visible=True\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27",
   "metadata": {},
   "source": [
    "### Add WMS layer\n",
    "\n",
    "You can add WMS layers to the map using the `add_wms_layer` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-74.5447, 40.6892], zoom=8, style=\"streets\")\n",
    "url = \"https://img.nj.gov/imagerywms/Natural2015\"\n",
    "layers = \"Natural2015\"\n",
    "m.add_wms_layer(url, layers=layers, before_id=\"aeroway_fill\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "29",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100.307965, 46.98692], zoom=13, pitch=45, style=\"3d-hybrid\")\n",
    "url = \"https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer\"\n",
    "m.add_wms_layer(url, layers=\"1\", name=\"NWI\", opacity=0.6)\n",
    "m.add_layer_control(bg_layers=True)\n",
    "m.add_legend(builtin_legend=\"NWI\", title=\"Wetland Type\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30",
   "metadata": {},
   "source": [
    "### MapTiler styles\n",
    "\n",
    "You can use any named style from MapTiler by setting the style parameter to the name of the style.\n",
    "\n",
    "![](https://i.imgur.com/dp2HxR2.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"streets\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"satellite\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"hybrid\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"topo\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35",
   "metadata": {},
   "source": [
    "## 3D mapping\n",
    "\n",
    "### 3D terrain\n",
    "\n",
    "MapTiler provides a variety of basemaps and styles that can be used to create 3D maps. You can use any styles from the MapTiler basemap gallery and prefix the style name with `3d-`. For example, `3d-hybrid`, `3d-satellite`, or `3d-topo`. To use the hillshade only, you can use the `3d-hillshade` style."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"3d-hybrid\")\n",
    "m.add_layer_control(bg_layers=True)\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"3d-satellite\")\n",
    "m.add_layer_control(bg_layers=True)\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"3d-topo\", exaggeration=1.5, hillshade=False)\n",
    "m.add_layer_control(bg_layers=True)\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"3d-ocean\", exaggeration=1.5, hillshade=True)\n",
    "m.add_layer_control(bg_layers=True)\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40",
   "metadata": {},
   "source": [
    "### 3D buildings\n",
    "\n",
    "You can add 3D buildings to the map using the `add_3d_buildings` method. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=[-74.0066, 40.7135], zoom=16, pitch=45, bearing=-17, style=\"basic-v2\"\n",
    ")\n",
    "MAPTILER_KEY = leafmap.get_api_key(\"MAPTILER_KEY\")\n",
    "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n",
    "source = {\n",
    "    \"url\": f\"https://api.maptiler.com/tiles/v3/tiles.json?key={MAPTILER_KEY}\",\n",
    "    \"type\": \"vector\",\n",
    "}\n",
    "\n",
    "layer = {\n",
    "    \"id\": \"3d-buildings\",\n",
    "    \"source\": \"openmaptiles\",\n",
    "    \"source-layer\": \"building\",\n",
    "    \"type\": \"fill-extrusion\",\n",
    "    \"min-zoom\": 15,\n",
    "    \"paint\": {\n",
    "        \"fill-extrusion-color\": [\n",
    "            \"interpolate\",\n",
    "            [\"linear\"],\n",
    "            [\"get\", \"render_height\"],\n",
    "            0,\n",
    "            \"lightgray\",\n",
    "            200,\n",
    "            \"royalblue\",\n",
    "            400,\n",
    "            \"lightblue\",\n",
    "        ],\n",
    "        \"fill-extrusion-height\": [\n",
    "            \"interpolate\",\n",
    "            [\"linear\"],\n",
    "            [\"zoom\"],\n",
    "            15,\n",
    "            0,\n",
    "            16,\n",
    "            [\"get\", \"render_height\"],\n",
    "        ],\n",
    "        \"fill-extrusion-base\": [\n",
    "            \"case\",\n",
    "            [\">=\", [\"get\", \"zoom\"], 16],\n",
    "            [\"get\", \"render_min_height\"],\n",
    "            0,\n",
    "        ],\n",
    "    },\n",
    "}\n",
    "m.add_source(\"openmaptiles\", source)\n",
    "m.add_layer(layer)\n",
    "m.add_layer_control()\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=[-74.0066, 40.7135], zoom=16, pitch=45, bearing=-17, style=\"basic-v2\"\n",
    ")\n",
    "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n",
    "m.add_3d_buildings(min_zoom=15)\n",
    "m.add_layer_control()\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43",
   "metadata": {},
   "source": [
    "### 3D indoor mapping\n",
    "\n",
    "Let's visualize indoor mapping data using the `add_geojson` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44",
   "metadata": {},
   "outputs": [],
   "source": [
    "data = \"https://maplibre.org/maplibre-gl-js/docs/assets/indoor-3d-map.geojson\"\n",
    "gdf = leafmap.geojson_to_gdf(data)\n",
    "gdf.explore()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45",
   "metadata": {},
   "outputs": [],
   "source": [
    "gdf.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "46",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=(-87.61694, 41.86625), zoom=17, pitch=40, bearing=20, style=\"positron\"\n",
    ")\n",
    "m.add_basemap(\"OpenStreetMap.Mapnik\")\n",
    "m.add_geojson(\n",
    "    data,\n",
    "    layer_type=\"fill-extrusion\",\n",
    "    name=\"floorplan\",\n",
    "    paint={\n",
    "        \"fill-extrusion-color\": [\"get\", \"color\"],\n",
    "        \"fill-extrusion-height\": [\"get\", \"height\"],\n",
    "        \"fill-extrusion-base\": [\"get\", \"base_height\"],\n",
    "        \"fill-extrusion-opacity\": 0.5,\n",
    "    },\n",
    ")\n",
    "m.add_layer_control()\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47",
   "metadata": {},
   "source": [
    "## Visualize vector data\n",
    "\n",
    "Leafmap provides a variety of methods to visualize vector data on the map.\n",
    "\n",
    "### Point data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "49",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = (\n",
    "    \"https://github.com/opengeos/datasets/releases/download/world/world_cities.geojson\"\n",
    ")\n",
    "geojson = requests.get(url).json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"streets\")\n",
    "m.add_geojson(geojson, name=\"cities\")\n",
    "m.add_popup(\"cities\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "51",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"streets\")\n",
    "source = {\"type\": \"geojson\", \"data\": geojson}\n",
    "\n",
    "layer = {\n",
    "    \"id\": \"cities\",\n",
    "    \"type\": \"symbol\",\n",
    "    \"source\": \"point\",\n",
    "    \"layout\": {\n",
    "        \"icon-image\": \"marker_15\",\n",
    "        \"icon-size\": 1,\n",
    "    },\n",
    "}\n",
    "m.add_source(\"point\", source)\n",
    "m.add_layer(layer)\n",
    "m.add_popup(\"cities\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52",
   "metadata": {},
   "source": [
    "### Line data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-122.486052, 37.830348], zoom=15, style=\"streets\")\n",
    "\n",
    "source = {\n",
    "    \"type\": \"geojson\",\n",
    "    \"data\": {\n",
    "        \"type\": \"Feature\",\n",
    "        \"properties\": {},\n",
    "        \"geometry\": {\n",
    "            \"type\": \"LineString\",\n",
    "            \"coordinates\": [\n",
    "                [-122.48369693756104, 37.83381888486939],\n",
    "                [-122.48348236083984, 37.83317489144141],\n",
    "                [-122.48339653015138, 37.83270036637107],\n",
    "                [-122.48356819152832, 37.832056363179625],\n",
    "                [-122.48404026031496, 37.83114119107971],\n",
    "                [-122.48404026031496, 37.83049717427869],\n",
    "                [-122.48348236083984, 37.829920943955045],\n",
    "                [-122.48356819152832, 37.82954808664175],\n",
    "                [-122.48507022857666, 37.82944639795659],\n",
    "                [-122.48610019683838, 37.82880236636284],\n",
    "                [-122.48695850372314, 37.82931081282506],\n",
    "                [-122.48700141906738, 37.83080223556934],\n",
    "                [-122.48751640319824, 37.83168351665737],\n",
    "                [-122.48803138732912, 37.832158048267786],\n",
    "                [-122.48888969421387, 37.83297152392784],\n",
    "                [-122.48987674713133, 37.83263257682617],\n",
    "                [-122.49043464660643, 37.832937629287755],\n",
    "                [-122.49125003814696, 37.832429207817725],\n",
    "                [-122.49163627624512, 37.832564787218985],\n",
    "                [-122.49223709106445, 37.83337825839438],\n",
    "                [-122.49378204345702, 37.83368330777276],\n",
    "            ],\n",
    "        },\n",
    "    },\n",
    "}\n",
    "\n",
    "layer = {\n",
    "    \"id\": \"route\",\n",
    "    \"type\": \"line\",\n",
    "    \"source\": \"route\",\n",
    "    \"layout\": {\"line-join\": \"round\", \"line-cap\": \"round\"},\n",
    "    \"paint\": {\"line-color\": \"#888\", \"line-width\": 8},\n",
    "}\n",
    "m.add_source(\"route\", source)\n",
    "m.add_layer(layer)\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54",
   "metadata": {},
   "source": [
    "### Polygon data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-68.137343, 45.137451], zoom=5, style=\"streets\")\n",
    "geojson = {\n",
    "    \"type\": \"Feature\",\n",
    "    \"geometry\": {\n",
    "        \"type\": \"Polygon\",\n",
    "        \"coordinates\": [\n",
    "            [\n",
    "                [-67.13734351262877, 45.137451890638886],\n",
    "                [-66.96466, 44.8097],\n",
    "                [-68.03252, 44.3252],\n",
    "                [-69.06, 43.98],\n",
    "                [-70.11617, 43.68405],\n",
    "                [-70.64573401557249, 43.090083319667144],\n",
    "                [-70.75102474636725, 43.08003225358635],\n",
    "                [-70.79761105007827, 43.21973948828747],\n",
    "                [-70.98176001655037, 43.36789581966826],\n",
    "                [-70.94416541205806, 43.46633942318431],\n",
    "                [-71.08482, 45.3052400000002],\n",
    "                [-70.6600225491012, 45.46022288673396],\n",
    "                [-70.30495378282376, 45.914794623389355],\n",
    "                [-70.00014034695016, 46.69317088478567],\n",
    "                [-69.23708614772835, 47.44777598732787],\n",
    "                [-68.90478084987546, 47.184794623394396],\n",
    "                [-68.23430497910454, 47.35462921812177],\n",
    "                [-67.79035274928509, 47.066248887716995],\n",
    "                [-67.79141211614706, 45.702585354182816],\n",
    "                [-67.13734351262877, 45.137451890638886],\n",
    "            ]\n",
    "        ],\n",
    "    },\n",
    "}\n",
    "source = {\"type\": \"geojson\", \"data\": geojson}\n",
    "m.add_source(\"maine\", source)\n",
    "layer = {\n",
    "    \"id\": \"maine\",\n",
    "    \"type\": \"fill\",\n",
    "    \"source\": \"maine\",\n",
    "    \"layout\": {},\n",
    "    \"paint\": {\"fill-color\": \"#088\", \"fill-opacity\": 0.8},\n",
    "}\n",
    "m.add_layer(layer)\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-68.137343, 45.137451], zoom=5, style=\"streets\")\n",
    "paint = {\"fill-color\": \"#088\", \"fill-opacity\": 0.8}\n",
    "m.add_geojson(geojson, layer_type=\"fill\", paint=paint)\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57",
   "metadata": {},
   "source": [
    "### Multiple geometries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=[-123.13, 49.254], zoom=11, style=\"dark-matter\", pitch=45, bearing=0\n",
    ")\n",
    "url = \"https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/geojson/vancouver-blocks.json\"\n",
    "paint_line = {\n",
    "    \"line-color\": \"white\",\n",
    "    \"line-width\": 2,\n",
    "}\n",
    "paint_fill = {\n",
    "    \"fill-extrusion-color\": {\n",
    "        \"property\": \"valuePerSqm\",\n",
    "        \"stops\": [\n",
    "            [0, \"grey\"],\n",
    "            [1000, \"yellow\"],\n",
    "            [5000, \"orange\"],\n",
    "            [10000, \"darkred\"],\n",
    "            [50000, \"lightblue\"],\n",
    "        ],\n",
    "    },\n",
    "    \"fill-extrusion-height\": [\"*\", 10, [\"sqrt\", [\"get\", \"valuePerSqm\"]]],\n",
    "    \"fill-extrusion-opacity\": 0.9,\n",
    "}\n",
    "m.add_geojson(url, layer_type=\"line\", paint=paint_line, name=\"blocks-line\")\n",
    "m.add_geojson(url, layer_type=\"fill-extrusion\", paint=paint_fill, name=\"blocks-fill\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.layer_interact()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60",
   "metadata": {},
   "source": [
    "### Marker cluster\n",
    "\n",
    "Create a marker cluster layer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "61",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-103.59179, 40.66995], zoom=3, style=\"streets\")\n",
    "data = \"https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson\"\n",
    "source_args = {\n",
    "    \"cluster\": True,\n",
    "    \"cluster_radius\": 50,\n",
    "    \"cluster_min_points\": 2,\n",
    "    \"cluster_max_zoom\": 14,\n",
    "    \"cluster_properties\": {\n",
    "        \"maxMag\": [\"max\", [\"get\", \"mag\"]],\n",
    "        \"minMag\": [\"min\", [\"get\", \"mag\"]],\n",
    "    },\n",
    "}\n",
    "\n",
    "m.add_geojson(\n",
    "    data,\n",
    "    layer_type=\"circle\",\n",
    "    name=\"earthquake-circles\",\n",
    "    filter=[\"!\", [\"has\", \"point_count\"]],\n",
    "    paint={\"circle-color\": \"darkblue\"},\n",
    "    source_args=source_args,\n",
    ")\n",
    "\n",
    "m.add_geojson(\n",
    "    data,\n",
    "    layer_type=\"circle\",\n",
    "    name=\"earthquake-clusters\",\n",
    "    filter=[\"has\", \"point_count\"],\n",
    "    paint={\n",
    "        \"circle-color\": [\n",
    "            \"step\",\n",
    "            [\"get\", \"point_count\"],\n",
    "            \"#51bbd6\",\n",
    "            100,\n",
    "            \"#f1f075\",\n",
    "            750,\n",
    "            \"#f28cb1\",\n",
    "        ],\n",
    "        \"circle-radius\": [\"step\", [\"get\", \"point_count\"], 20, 100, 30, 750, 40],\n",
    "    },\n",
    "    source_args=source_args,\n",
    ")\n",
    "\n",
    "m.add_geojson(\n",
    "    data,\n",
    "    layer_type=\"symbol\",\n",
    "    name=\"earthquake-labels\",\n",
    "    filter=[\"has\", \"point_count\"],\n",
    "    layout={\n",
    "        \"text-field\": [\"get\", \"point_count_abbreviated\"],\n",
    "        \"text-size\": 12,\n",
    "    },\n",
    "    source_args=source_args,\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62",
   "metadata": {},
   "source": [
    "### Local vector data\n",
    "\n",
    "You can load local vector data interactively using the `open_geojson` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "63",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100, 40], zoom=3)\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = \"https://github.com/opengeos/datasets/releases/download/us/us_states.geojson\"\n",
    "filepath = \"data/us_states.geojson\"\n",
    "leafmap.download_file(url, filepath, quiet=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "65",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.open_geojson()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66",
   "metadata": {},
   "source": [
    "### Live feature update\n",
    "\n",
    "#### Animate a point along a route"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "67",
   "metadata": {},
   "outputs": [],
   "source": [
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "68",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"streets\")\n",
    "url = \"https://github.com/opengeos/datasets/releases/download/us/arc_with_bearings.geojson\"\n",
    "geojson = requests.get(url).json()\n",
    "bearings = geojson[\"features\"][0][\"properties\"][\"bearings\"]\n",
    "coordinates = geojson[\"features\"][0][\"geometry\"][\"coordinates\"][:-1]\n",
    "m.add_geojson(geojson, name=\"route\")\n",
    "\n",
    "origin = [-122.414, 37.776]\n",
    "destination = [-77.032, 38.913]\n",
    "\n",
    "point = {\n",
    "    \"type\": \"FeatureCollection\",\n",
    "    \"features\": [\n",
    "        {\n",
    "            \"type\": \"Feature\",\n",
    "            \"properties\": {},\n",
    "            \"geometry\": {\"type\": \"Point\", \"coordinates\": origin},\n",
    "        }\n",
    "    ],\n",
    "}\n",
    "source = {\"type\": \"geojson\", \"data\": point}\n",
    "m.add_source(\"point\", source)\n",
    "layer = {\n",
    "    \"id\": \"point\",\n",
    "    \"source\": \"point\",\n",
    "    \"type\": \"symbol\",\n",
    "    \"layout\": {\n",
    "        \"icon-image\": \"airport_15\",\n",
    "        \"icon-rotate\": [\"get\", \"bearing\"],\n",
    "        \"icon-rotation-alignment\": \"map\",\n",
    "        \"icon-overlap\": \"always\",\n",
    "        \"icon-ignore-placement\": True,\n",
    "    },\n",
    "}\n",
    "m.add_layer(layer)\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "69",
   "metadata": {},
   "outputs": [],
   "source": [
    "for index, coordinate in enumerate(coordinates):\n",
    "    point[\"features\"][0][\"geometry\"][\"coordinates\"] = coordinate\n",
    "    point[\"features\"][0][\"properties\"][\"bearing\"] = bearings[index]\n",
    "    m.set_data(\"point\", point)\n",
    "    time.sleep(0.05)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70",
   "metadata": {},
   "source": [
    "#### Update a feature in realtime"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "71",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-122.019807, 45.632433], zoom=14, pitch=60, style=\"3d-terrain\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "72",
   "metadata": {},
   "outputs": [],
   "source": [
    "import geopandas as gpd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = \"https://maplibre.org/maplibre-gl-js/docs/assets/hike.geojson\"\n",
    "gdf = gpd.read_file(url)\n",
    "coordinates = list(gdf.geometry[0].coords)\n",
    "print(coordinates[:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74",
   "metadata": {},
   "outputs": [],
   "source": [
    "source = {\n",
    "    \"type\": \"geojson\",\n",
    "    \"data\": {\n",
    "        \"type\": \"Feature\",\n",
    "        \"geometry\": {\"type\": \"LineString\", \"coordinates\": [coordinates[0]]},\n",
    "    },\n",
    "}\n",
    "m.add_source(\"trace\", source)\n",
    "layer = {\n",
    "    \"id\": \"trace\",\n",
    "    \"type\": \"line\",\n",
    "    \"source\": \"trace\",\n",
    "    \"paint\": {\"line-color\": \"yellow\", \"line-opacity\": 0.75, \"line-width\": 5},\n",
    "}\n",
    "m.add_layer(layer)\n",
    "m.jump_to({\"center\": coordinates[0], \"zoom\": 14})\n",
    "m.set_pitch(30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "75",
   "metadata": {},
   "outputs": [],
   "source": [
    "for coord in coordinates:\n",
    "    time.sleep(0.005)\n",
    "    source[\"data\"][\"geometry\"][\"coordinates\"].append(coord)\n",
    "    m.set_data(\"trace\", source[\"data\"])\n",
    "    m.pan_to(coord)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76",
   "metadata": {},
   "source": [
    "## Visualize raster data\n",
    "\n",
    "### Local raster data\n",
    "\n",
    "You can load local raster data using the `add_raster` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = \"https://github.com/opengeos/datasets/releases/download/raster/landsat.tif\"\n",
    "filepath = \"landsat.tif\"\n",
    "leafmap.download_file(url, filepath)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "78",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"streets\")\n",
    "m.add_raster(filepath, indexes=[3, 2, 1], vmin=0, vmax=100, name=\"Landsat-321\")\n",
    "m.add_raster(filepath, indexes=[4, 3, 2], vmin=0, vmax=100, name=\"Landsat-432\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.layer_interact()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "80",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = \"https://github.com/opengeos/datasets/releases/download/raster/srtm90.tif\"\n",
    "filepath = \"srtm90.tif\"\n",
    "leafmap.download_file(url, filepath)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"satellite\")\n",
    "m.add_raster(filepath, colormap=\"terrain\", name=\"DEM\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "82",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.layer_interact()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83",
   "metadata": {},
   "source": [
    "### Cloud Optimized GeoTIFF (COG)\n",
    "\n",
    "You can load Cloud Optimized GeoTIFF (COG) data using the `add_cog_layer` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "84",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map()\n",
    "m.add_basemap(\"Esri.WorldImagery\")\n",
    "url = (\n",
    "    \"https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-09-13.tif\"\n",
    ")\n",
    "m.add_cog_layer(url, name=\"COG\", attribution=\"Maxar\", fit_bounds=True, nodata=0)\n",
    "m.add_layer_control()\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "85",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.layer_interact()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86",
   "metadata": {},
   "source": [
    "### STAC layer\n",
    "\n",
    "You can load SpatioTemporal Asset Catalog (STAC) data using the `add_stac_layer` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "87",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map()\n",
    "url = \"https://canada-spot-ortho.s3.amazonaws.com/canada_spot_orthoimages/canada_spot5_orthoimages/S5_2007/S5_11055_6057_20070622/S5_11055_6057_20070622.json\"\n",
    "m.add_stac_layer(url, bands=[\"B4\", \"B3\", \"B2\"], name=\"SPOT\", vmin=0, vmax=150, nodata=0)\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88",
   "metadata": {},
   "source": [
    "## PMTiles\n",
    "\n",
    "Leafmap supports the [PMTiles](https://protomaps.com/docs/pmtiles/) format for fast and efficient rendering of vector tiles."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89",
   "metadata": {},
   "source": [
    "### Overture data\n",
    "\n",
    "You can also visualize Overture data. Inspired by [overture-maps](https://github.com/tebben/overture-maps)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "90",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = \"https://storage.googleapis.com/ahp-research/overture/pmtiles/overture.pmtiles\"\n",
    "metadata = leafmap.pmtiles_metadata(url)\n",
    "print(f\"layer names: {metadata['layer_names']}\")\n",
    "print(f\"bounds: {metadata['bounds']}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(height=\"800px\")\n",
    "m.add_basemap(\"Esri.WorldImagery\")\n",
    "\n",
    "style = {\n",
    "    \"version\": 8,\n",
    "    \"sources\": {\n",
    "        \"example_source\": {\n",
    "            \"type\": \"vector\",\n",
    "            \"url\": \"pmtiles://\" + url,\n",
    "            \"attribution\": \"PMTiles\",\n",
    "        }\n",
    "    },\n",
    "    \"layers\": [\n",
    "        # {\n",
    "        #     \"id\": \"admins\",\n",
    "        #     \"source\": \"example_source\",\n",
    "        #     \"source-layer\": \"admins\",\n",
    "        #     \"type\": \"fill\",\n",
    "        #     \"paint\": {\"fill-color\": \"#BDD3C7\", \"fill-opacity\": 0.1},\n",
    "        # },\n",
    "        {\n",
    "            \"id\": \"buildings\",\n",
    "            \"source\": \"example_source\",\n",
    "            \"source-layer\": \"buildings\",\n",
    "            \"type\": \"fill\",\n",
    "            \"paint\": {\"fill-color\": \"#FFFFB3\", \"fill-opacity\": 0.5},\n",
    "        },\n",
    "        {\n",
    "            \"id\": \"places\",\n",
    "            \"source\": \"example_source\",\n",
    "            \"source-layer\": \"places\",\n",
    "            \"type\": \"fill\",\n",
    "            \"paint\": {\"fill-color\": \"#BEBADA\", \"fill-opacity\": 0.5},\n",
    "        },\n",
    "        {\n",
    "            \"id\": \"roads\",\n",
    "            \"source\": \"example_source\",\n",
    "            \"source-layer\": \"roads\",\n",
    "            \"type\": \"line\",\n",
    "            \"paint\": {\"line-color\": \"#FB8072\"},\n",
    "        },\n",
    "    ],\n",
    "}\n",
    "\n",
    "# style = leafmap.pmtiles_style(url)  # Use default style\n",
    "\n",
    "m.add_pmtiles(\n",
    "    url,\n",
    "    style=style,\n",
    "    visible=True,\n",
    "    opacity=1.0,\n",
    "    tooltip=True,\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "92",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.layer_interact()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93",
   "metadata": {},
   "source": [
    "### Source Cooperative\n",
    "\n",
    "Let's visualize the [Google-Microsoft Open Buildings - combined by VIDA](https://beta.source.coop/repositories/vida/google-microsoft-open-buildings/description)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "94",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = \"https://data.source.coop/vida/google-microsoft-open-buildings/pmtiles/go_ms_building_footprints.pmtiles\"\n",
    "metadata = leafmap.pmtiles_metadata(url)\n",
    "print(f\"layer names: {metadata['layer_names']}\")\n",
    "print(f\"bounds: {metadata['bounds']}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "95",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[0, 20], zoom=2, height=\"800px\")\n",
    "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n",
    "\n",
    "style = {\n",
    "    \"version\": 8,\n",
    "    \"sources\": {\n",
    "        \"example_source\": {\n",
    "            \"type\": \"vector\",\n",
    "            \"url\": \"pmtiles://\" + url,\n",
    "            \"attribution\": \"PMTiles\",\n",
    "        }\n",
    "    },\n",
    "    \"layers\": [\n",
    "        {\n",
    "            \"id\": \"buildings\",\n",
    "            \"source\": \"example_source\",\n",
    "            \"source-layer\": \"building_footprints\",\n",
    "            \"type\": \"fill\",\n",
    "            \"paint\": {\"fill-color\": \"#3388ff\", \"fill-opacity\": 0.5},\n",
    "        },\n",
    "    ],\n",
    "}\n",
    "\n",
    "# style = leafmap.pmtiles_style(url)  # Use default style\n",
    "\n",
    "m.add_pmtiles(\n",
    "    url,\n",
    "    style=style,\n",
    "    visible=True,\n",
    "    opacity=1.0,\n",
    "    tooltip=True,\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "96",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.layer_interact()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97",
   "metadata": {},
   "source": [
    "### 3D PMTiles\n",
    "\n",
    "Visualize the global building data in 3D."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "98",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = \"https://data.source.coop/cholmes/overture/overture-buildings.pmtiles\"\n",
    "metadata = leafmap.pmtiles_metadata(url)\n",
    "print(f\"layer names: {metadata['layer_names']}\")\n",
    "print(f\"bounds: {metadata['bounds']}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=[-74.0095, 40.7046], zoom=16, pitch=60, bearing=-17, style=\"positron\"\n",
    ")\n",
    "m.add_basemap(\"OpenStreetMap.Mapnik\")\n",
    "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n",
    "\n",
    "style = {\n",
    "    \"layers\": [\n",
    "        {\n",
    "            \"id\": \"buildings\",\n",
    "            \"source\": \"example_source\",\n",
    "            \"source-layer\": \"buildings\",\n",
    "            \"type\": \"fill-extrusion\",\n",
    "            \"filter\": [\n",
    "                \">\",\n",
    "                [\"get\", \"height\"],\n",
    "                0,\n",
    "            ],  # only show buildings with height info\n",
    "            \"paint\": {\n",
    "                \"fill-extrusion-color\": [\n",
    "                    \"interpolate\",\n",
    "                    [\"linear\"],\n",
    "                    [\"get\", \"height\"],\n",
    "                    0,\n",
    "                    \"lightgray\",\n",
    "                    200,\n",
    "                    \"royalblue\",\n",
    "                    400,\n",
    "                    \"lightblue\",\n",
    "                ],\n",
    "                \"fill-extrusion-height\": [\"*\", [\"get\", \"height\"], 1],\n",
    "            },\n",
    "        },\n",
    "    ],\n",
    "}\n",
    "\n",
    "m.add_pmtiles(\n",
    "    url,\n",
    "    style=style,\n",
    "    visible=True,\n",
    "    opacity=1.0,\n",
    "    tooltip=True,\n",
    "    template=\"Height: {{height}}<br>Country: {{country_iso}}\",\n",
    "    fit_bounds=False,\n",
    ")\n",
    "m.add_layer_control()\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "100",
   "metadata": {},
   "source": [
    "## Add custom components\n",
    "\n",
    "You can add custom components to the map, including images, videos, text, color bar, and legend.\n",
    "\n",
    "### Add image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "101",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[0.349419, -1.80921], zoom=3, style=\"streets\")\n",
    "image = \"https://upload.wikimedia.org/wikipedia/commons/7/7c/201408_cat.png\"\n",
    "source = {\n",
    "    \"type\": \"geojson\",\n",
    "    \"data\": {\n",
    "        \"type\": \"FeatureCollection\",\n",
    "        \"features\": [\n",
    "            {\"type\": \"Feature\", \"geometry\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}\n",
    "        ],\n",
    "    },\n",
    "}\n",
    "\n",
    "layer = {\n",
    "    \"id\": \"points\",\n",
    "    \"type\": \"symbol\",\n",
    "    \"source\": \"point\",\n",
    "    \"layout\": {\n",
    "        \"icon-image\": \"cat\",\n",
    "        \"icon-size\": 0.25,\n",
    "        \"text-field\": \"I love kitty!\",\n",
    "        \"text-font\": [\"Open Sans Regular\"],\n",
    "        \"text-offset\": [0, 3],\n",
    "        \"text-anchor\": \"top\",\n",
    "    },\n",
    "}\n",
    "m.add_image(\"cat\", image)\n",
    "m.add_source(\"point\", source)\n",
    "m.add_layer(layer)\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "102",
   "metadata": {},
   "source": [
    "### Add text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "103",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"streets\")\n",
    "text = \"Hello World\"\n",
    "m.add_text(text, fontsize=20, position=\"bottom-right\")\n",
    "text2 = \"Awesome Text!\"\n",
    "m.add_text(text2, fontsize=25, bg_color=\"rgba(255, 255, 255, 0.8)\", position=\"top-left\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "104",
   "metadata": {},
   "source": [
    "### Add GIF"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "105",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n",
    "image = \"https://i.imgur.com/KeiAsTv.gif\"\n",
    "m.add_image(image=image, width=250, height=250, position=\"bottom-right\")\n",
    "text = \"I love sloth!🦥\"\n",
    "m.add_text(text, fontsize=35, padding=\"20px\")\n",
    "image2 = \"https://i.imgur.com/kZC2tpr.gif\"\n",
    "m.add_image(image=image2, bg_color=\"transparent\", position=\"bottom-left\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "106",
   "metadata": {},
   "source": [
    "### Add HTML"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "107",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n",
    "html = \"\"\"\n",
    "<html>\n",
    "<style>\n",
    "body {\n",
    "  font-size: 20px;\n",
    "}\n",
    "</style>\n",
    "<body>\n",
    "\n",
    "<span style='font-size:100px;'>&#128640;</span>\n",
    "<p>I will display &#128641;</p>\n",
    "<p>I will display &#128642;</p>\n",
    "\n",
    "</body>\n",
    "</html>\n",
    "\"\"\"\n",
    "m.add_html(html, bg_color=\"transparent\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "108",
   "metadata": {},
   "source": [
    "### Add colorbar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "109",
   "metadata": {},
   "outputs": [],
   "source": [
    "dem = \"https://github.com/opengeos/datasets/releases/download/raster/srtm90.tif\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "110",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"streets\")\n",
    "m.add_cog_layer(\n",
    "    dem,\n",
    "    name=\"DEM\",\n",
    "    colormap_name=\"terrain\",\n",
    "    rescale=\"0, 4000\",\n",
    "    fit_bounds=True,\n",
    "    nodata=0,\n",
    ")\n",
    "m.add_colorbar(\n",
    "    cmap=\"terrain\", vmin=0, vmax=4000, label=\"Elevation (m)\", position=\"bottom-right\"\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "111",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"streets\")\n",
    "m.add_cog_layer(\n",
    "    dem,\n",
    "    name=\"DEM\",\n",
    "    colormap_name=\"terrain\",\n",
    "    rescale=\"0, 4000\",\n",
    "    nodata=0,\n",
    "    fit_bounds=True,\n",
    ")\n",
    "m.add_colorbar(\n",
    "    cmap=\"terrain\",\n",
    "    vmin=0,\n",
    "    vmax=4000,\n",
    "    label=\"Elevation (m)\",\n",
    "    position=\"bottom-right\",\n",
    "    transparent=True,\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "112",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(style=\"streets\")\n",
    "m.add_cog_layer(\n",
    "    dem,\n",
    "    name=\"DEM\",\n",
    "    colormap_name=\"terrain\",\n",
    "    rescale=\"0, 4000\",\n",
    "    nodata=0,\n",
    "    fit_bounds=True,\n",
    ")\n",
    "m.add_colorbar(\n",
    "    cmap=\"terrain\",\n",
    "    vmin=0,\n",
    "    vmax=4000,\n",
    "    label=\"Elevation (m)\",\n",
    "    position=\"bottom-right\",\n",
    "    width=0.2,\n",
    "    height=3,\n",
    "    orientation=\"vertical\",\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "113",
   "metadata": {},
   "source": [
    "### Add legend"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "114",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n",
    "m.add_basemap(\"Esri.WorldImagery\")\n",
    "url = \"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms\"\n",
    "layers = \"NLCD_2021_Land_Cover_L48\"\n",
    "m.add_wms_layer(url, layers=layers, name=\"NLCD 2021\")\n",
    "m.add_legend(\n",
    "    title=\"NLCD Land Cover Type\",\n",
    "    builtin_legend=\"NLCD\",\n",
    "    bg_color=\"rgba(255, 255, 255, 0.5)\",\n",
    "    position=\"bottom-left\",\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "115",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n",
    "m.add_basemap(\"Esri.WorldImagery\")\n",
    "url = \"https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer\"\n",
    "m.add_wms_layer(url, layers=\"1\", name=\"NWI\", opacity=0.6)\n",
    "m.add_layer_control()\n",
    "m.add_legend(builtin_legend=\"NWI\", title=\"Wetland Type\")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "116",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n",
    "m.add_basemap(\"Esri.WorldImagery\")\n",
    "url = \"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms\"\n",
    "layers = \"NLCD_2021_Land_Cover_L48\"\n",
    "m.add_wms_layer(url, layers=layers, name=\"NLCD 2021\")\n",
    "\n",
    "legend_dict = {\n",
    "    \"11 Open Water\": \"466b9f\",\n",
    "    \"12 Perennial Ice/Snow\": \"d1def8\",\n",
    "    \"21 Developed, Open Space\": \"dec5c5\",\n",
    "    \"22 Developed, Low Intensity\": \"d99282\",\n",
    "    \"23 Developed, Medium Intensity\": \"eb0000\",\n",
    "    \"24 Developed High Intensity\": \"ab0000\",\n",
    "    \"31 Barren Land (Rock/Sand/Clay)\": \"b3ac9f\",\n",
    "    \"41 Deciduous Forest\": \"68ab5f\",\n",
    "    \"42 Evergreen Forest\": \"1c5f2c\",\n",
    "    \"43 Mixed Forest\": \"b5c58f\",\n",
    "    \"51 Dwarf Scrub\": \"af963c\",\n",
    "    \"52 Shrub/Scrub\": \"ccb879\",\n",
    "    \"71 Grassland/Herbaceous\": \"dfdfc2\",\n",
    "    \"72 Sedge/Herbaceous\": \"d1d182\",\n",
    "    \"73 Lichens\": \"a3cc51\",\n",
    "    \"74 Moss\": \"82ba9e\",\n",
    "    \"81 Pasture/Hay\": \"dcd939\",\n",
    "    \"82 Cultivated Crops\": \"ab6c28\",\n",
    "    \"90 Woody Wetlands\": \"b8d9eb\",\n",
    "    \"95 Emergent Herbaceous Wetlands\": \"6c9fb8\",\n",
    "}\n",
    "m.add_legend(\n",
    "    title=\"NLCD Land Cover Type\",\n",
    "    legend_dict=legend_dict,\n",
    "    bg_color=\"rgba(255, 255, 255, 0.5)\",\n",
    "    position=\"bottom-left\",\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "117",
   "metadata": {},
   "source": [
    "### Add video\n",
    "\n",
    "The `urls` value is an array. For each URL in the array, a video element source will be created. To support the video across browsers, supply URLs in multiple formats.\n",
    "The `coordinates` array contains [longitude, latitude] pairs for the video corners listed in clockwise order: top left, top right, bottom right, bottom left."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "118",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=[-122.514426, 37.562984], zoom=17, bearing=-96, style=\"satellite\"\n",
    ")\n",
    "urls = [\n",
    "    \"https://static-assets.mapbox.com/mapbox-gl-js/drone.mp4\",\n",
    "    \"https://static-assets.mapbox.com/mapbox-gl-js/drone.webm\",\n",
    "]\n",
    "coordinates = [\n",
    "    [-122.51596391201019, 37.56238816766053],\n",
    "    [-122.51467645168304, 37.56410183312965],\n",
    "    [-122.51309394836426, 37.563391708549425],\n",
    "    [-122.51423120498657, 37.56161849366671],\n",
    "]\n",
    "m.add_video(urls, coordinates)\n",
    "m.add_layer_control()\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "119",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(center=[-115, 25], zoom=4, style=\"satellite\")\n",
    "urls = [\n",
    "    \"https://data.opengeos.org/patricia_nasa.mp4\",\n",
    "    \"https://data.opengeos.org/patricia_nasa.webm\",\n",
    "]\n",
    "coordinates = [\n",
    "    [-130, 32],\n",
    "    [-100, 32],\n",
    "    [-100, 13],\n",
    "    [-130, 13],\n",
    "]\n",
    "m.add_video(urls, coordinates)\n",
    "m.add_layer_control()\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "120",
   "metadata": {},
   "source": [
    "## Deck.GL layers\n",
    "\n",
    "Deck.GL layers can be added to the map using the `add_deck_layer` method.\n",
    "\n",
    "### Single Deck.GL layer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "121",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    style=\"positron\",\n",
    "    center=(-122.4, 37.74),\n",
    "    zoom=12,\n",
    "    pitch=40,\n",
    ")\n",
    "deck_grid_layer = {\n",
    "    \"@@type\": \"GridLayer\",\n",
    "    \"id\": \"GridLayer\",\n",
    "    \"data\": \"https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json\",\n",
    "    \"extruded\": True,\n",
    "    \"getPosition\": \"@@=COORDINATES\",\n",
    "    \"getColorWeight\": \"@@=SPACES\",\n",
    "    \"getElevationWeight\": \"@@=SPACES\",\n",
    "    \"elevationScale\": 4,\n",
    "    \"cellSize\": 200,\n",
    "    \"pickable\": True,\n",
    "}\n",
    "\n",
    "m.add_deck_layers([deck_grid_layer], tooltip=\"Number of points: {{ count }}\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "122",
   "metadata": {},
   "source": [
    "### Multiple Deck.GL layers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "123",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "124",
   "metadata": {},
   "outputs": [],
   "source": [
    "data = requests.get(\n",
    "    \"https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_airports.geojson\"\n",
    ").json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "125",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    style=\"positron\",\n",
    "    center=(0.45, 51.47),\n",
    "    zoom=4,\n",
    "    pitch=30,\n",
    ")\n",
    "deck_geojson_layer = {\n",
    "    \"@@type\": \"GeoJsonLayer\",\n",
    "    \"id\": \"airports\",\n",
    "    \"data\": data,\n",
    "    \"filled\": True,\n",
    "    \"pointRadiusMinPixels\": 2,\n",
    "    \"pointRadiusScale\": 2000,\n",
    "    \"getPointRadius\": \"@@=11 - properties.scalerank\",\n",
    "    \"getFillColor\": [200, 0, 80, 180],\n",
    "    \"autoHighlight\": True,\n",
    "    \"pickable\": True,\n",
    "}\n",
    "\n",
    "deck_arc_layer = {\n",
    "    \"@@type\": \"ArcLayer\",\n",
    "    \"id\": \"arcs\",\n",
    "    \"data\": [\n",
    "        feature\n",
    "        for feature in data[\"features\"]\n",
    "        if feature[\"properties\"][\"scalerank\"] < 4\n",
    "    ],\n",
    "    \"getSourcePosition\": [-0.4531566, 51.4709959],  # London\n",
    "    \"getTargetPosition\": \"@@=geometry.coordinates\",\n",
    "    \"getSourceColor\": [0, 128, 200],\n",
    "    \"getTargetColor\": [200, 0, 80],\n",
    "    \"getWidth\": 2,\n",
    "    \"pickable\": True,\n",
    "}\n",
    "\n",
    "m.add_deck_layers(\n",
    "    [deck_geojson_layer, deck_arc_layer],\n",
    "    tooltip={\n",
    "        \"airports\": \"{{ &properties.name }}\",\n",
    "        \"arcs\": \"gps_code: {{ properties.gps_code }}\",\n",
    "    },\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "126",
   "metadata": {},
   "source": [
    "## Google Earth Engine\n",
    "\n",
    "You can use the Earth Engine Python API to load and visualize Earth Engine data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "127",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=[-120.4482, 38.0399], zoom=13, pitch=60, bearing=30, style=\"3d-terrain\"\n",
    ")\n",
    "m.add_ee_layer(asset_id=\"ESA/WorldCover/v200\", opacity=0.5)\n",
    "m.add_legend(builtin_legend=\"ESA_WorldCover\", title=\"ESA Landcover\")\n",
    "m.add_layer_control()\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "128",
   "metadata": {},
   "outputs": [],
   "source": [
    "m.layer_interact()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "129",
   "metadata": {},
   "source": [
    "We can also overlay other data layers on top of Earth Engine data layers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "130",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=[-74.012998, 40.70414], zoom=15.6, pitch=60, bearing=30, style=\"3d-terrain\"\n",
    ")\n",
    "m.add_ee_layer(asset_id=\"ESA/WorldCover/v200\", opacity=0.5)\n",
    "m.add_3d_buildings()\n",
    "m.add_legend(builtin_legend=\"ESA_WorldCover\", title=\"ESA Landcover\")\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "131",
   "metadata": {},
   "source": [
    "If you have an Earth Engine, you can uncomment the first two code blocks to add any Earth Engine datasets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "132",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import ee\n",
    "# ee.Initialize(project=\"YOUR-PROJECT-ID\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "133",
   "metadata": {},
   "outputs": [],
   "source": [
    "# m = leafmap.Map(center=[-120.4482, 38.03994], zoom=13, pitch=60, bearing=30, style=\"3d-terrain\")\n",
    "# dataset = ee.ImageCollection(\"ESA/WorldCover/v200\").first()\n",
    "# vis_params = {\"bands\": [\"Map\"]}\n",
    "# m.add_ee_layer(dataset, vis_params, name=\"ESA Worldcover\", opacity=0.5)\n",
    "# m.add_legend(builtin_legend=\"ESA_WorldCover\", title=\"ESA Landcover\")\n",
    "# m.add_layer_control()\n",
    "# m"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "134",
   "metadata": {},
   "source": [
    "## To HTML\n",
    "\n",
    "To export the map as an HTML file, use the `to_html` method. To avoid exposing your private API key, you should create a public API key and restrict it to your website domain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "135",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import os\n",
    "# os.environ[\"MAPTILER_KEY\"] = \"YOUR_PRIVATE_API_KEY\"\n",
    "# os.environ[\"MAPTILER_KEY_PUBLIC\"] = \"YOUR_PUBLIC_API_KEY\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "136",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=[-122.19861, 46.21168], zoom=13, pitch=60, bearing=150, style=\"3d-terrain\"\n",
    ")\n",
    "m.add_layer_control(bg_layers=True)\n",
    "m.to_html(\n",
    "    \"terrain.html\",\n",
    "    title=\"Awesome 3D Map\",\n",
    "    width=\"100%\",\n",
    "    height=\"100%\",\n",
    "    replace_key=True,\n",
    ")\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "137",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = leafmap.Map(\n",
    "    center=[-74.0066, 40.7135], zoom=16, pitch=45, bearing=-17, style=\"basic-v2\"\n",
    ")\n",
    "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n",
    "m.add_3d_buildings(min_zoom=15)\n",
    "m.add_layer_control()\n",
    "m.to_html(\n",
    "    \"buildings.html\",\n",
    "    title=\"Awesome 3D Map\",\n",
    "    width=\"100%\",\n",
    "    height=\"100%\",\n",
    "    replace_key=True,\n",
    ")\n",
    "m"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
